# -*- coding: ISO-8859-1 -*-
#############################################
## (C)opyright by Dirk Holtwick, 2002-2007 ##
## All rights reserved                     ##
#############################################

__reversion__ = "$Revision: 20 $"
__author__    = "$Author: holtwick $"
__date__      = "$Date: 2007-10-09 12:58:24 +0200 (Di, 09 Okt 2007) $"

from xml.dom import Node
import xml.dom.minidom

#from xhtml2pdf.config import *
from xhtml2pdf.utils import getSize, getColor
from xhtml2pdf.dom import getAttributes
from xhtml2pdf.loader import getFile
from xhtml2pdf.css import css, cssDOMElementInterface

import logging
log = logging.getLogger(__name__)

# rxhttpstrip = re.compile("http://[^/]+(.*)", re.M|re.I)

CSS_ATTRIBUTES = '''
    color
    font-family
    font-size
    font-weight
    font-style
    text-decoration
    line-height
    background-color
    display
    margin-left
    margin-right
    margin-top
    margin-bottom
    padding-left
    padding-right
    padding-top
    padding-bottom
    border-top-color
    border-top-style
    border-top-width
    border-bottom-color
    border-bottom-style
    border-bottom-width
    border-left-color
    border-left-style
    border-left-width
    border-right-color
    border-right-style
    border-right-width
    text-align
    vertical-align
    width
    height
    zoom
    page-break-after
    page-break-before
    list-style-type
    white-space
    text-indent
    -pdf-page-break
    -pdf-frame-break
    -pdf-next-page
    -pdf-keep-with-next
    -pdf-outline
    -pdf-outline-level
    -pdf-outline-open
    -pdf-line-spacing
    -pdf-keep-in-frame-mode
    '''.strip().split()

def getCSSAttr(self, cssCascade, attrName, default=NotImplemented):
    if attrName in self.cssAttrs:
        return self.cssAttrs[attrName]
    try:
        result = cssCascade.findStyleFor(self.cssElement, attrName, default)
    except LookupError:
        result = None
    # XXX Workaround for inline styles
    try:
        style = self.cssStyle
    except:
        style = self.cssStyle = cssCascade.parser.parseInline(self.cssElement.getStyleAttr() or '')[0]
    if style.has_key(attrName):
        result = style[attrName]
    if result == 'inherit':
        if hasattr(self.parentNode, 'getCSSAttr'):
            result = self.parentNode.getCSSAttr(cssCascade, attrName, default)
        elif default is not NotImplemented:
            return default
        else:
            raise LookupError("Could not find inherited CSS attribute value for '%s'" % (attrName,))
    if result is not None:
        self.cssAttrs[attrName] = result
    return result

# All new elements should have this attribute
xml.dom.minidom.Element.getCSSAttr = getCSSAttr

def applyCSS(node, cssCascade, attributeNames=CSS_ATTRIBUTES):
    node.cssElement = cssDOMElementInterface.CSSDOMElementInterface(node)
    node.cssAttrs = {}
    # node.cssElement.onCSSParserVisit(c.cssCascade.parser)
    cssAttrMap = {}
    for cssAttrName in attributeNames:
        try:
            cssAttrMap[cssAttrName] = node.getCSSAttr(cssCascade, cssAttrName)
        #except LookupError:
        #    pass
        except Exception:
            log.debug("CSS error '%s'", cssAttrName, exc_info=1)
    return node.cssAttrs

def applyStyles(node, cssCascade):
    if node.nodeType == Node.ELEMENT_NODE:
        node.styleSource = applyCSS(node, cssCascade)
        node.style = getStyle(node.styleSource)
    for node in node.childNodes:
        applyStyles(node, cssCascade)
    return node

# Helper functions for CSS values

def setColor(name, value, data):
    return [(name, getColor(value))]

def setString(name, value, data):
    return [(name, str(value).lower())]

def setLineHeight(name, value, data):
    return [(name, getSize(value))]

def setSize(name, value, data):
    return [(name, getSize(value, style["font-size"]))]

def setTextDecoration(name, value, data):
    value = toList(value)
    return [
        ("underline", "underline" in value),
        ("strike", "line-through" in value)]

def setFontWeight(name, value):
    return [("bold", str(attr["font-weight"]).lower() in ("bold", "bolder", "500", "600", "700", "800", "900"))]

def setFontStyle(name, value):
    return [("italic", str(attr["font-style"]).lower() in ("italic", "oblique"))]

CSS_CONVERTER = {
    "color": setColor,
    "background-color": setColor,
    "line-height": setLineHeight,
    "line-height": setString, # XXX
    "text-decoration": setTextDecoration,
    "text-align": setString, # XXX
    "font-weight": setFontWeight,
    "font-style": setFontStyle,
    "text-indent": setSize,
    "white-space": setString,
    "margin-left": setSize,
    "margin-right": setSize,
    "margin-top": setSize,
    "margin-bottom": setSize,
    "padding-left": setSize,
    "padding-right": setSize,
    "padding-top": setSize,
    "padding-bottom": setSize,
    "border-top-color": setColor,
    "border-top-style": setString,
    "border-top-width": setSize,
    "border-bottom-color": setColor,
    "border-bottom-style": setString,
    "border-bottom-width": setSize,
    "border-left-color": setColor,
    "border-left-style": setString,
    "border-left-width": setSize,
    "border-right-color": setColor,
    "border-right-style": setString,
    "border-right-width": setSize,
}

def getStyle(attrs, converter=CSS_CONVERTER, data={"font-size": 10.0}):
    style = {}
    if "font-size" in attrs:
        style["font-size"] = max(getSize(
            "".join(attr["font-size"]),
            data.get("font-size", 0),
            data.get("base-font-size", None), 1.0))
    else:
        style["font-size"] = data.get("font-size", 0)
    for name, value in attrs.items():
        conv = converter.get(name, None)
        if conv:
            for k, v in conv(name, value, style):
                style[k] = v
    return style

    '''
    if attr.has_key("font-family"):
        frag.fontName = getFontName(attr["font-family"])
    if attr.has_key("line-height"):
        leading = "".join(attr["line-height"])
        frag.leading = getSize(leading, frag.fontSize)
        frag.leadingSource = leading
    else:
        frag.leading = getSize(frag.leadingSource, frag.fontSize)
    if attr.has_key("-pdf-line-spacing"):
        frag.leadingSpace = getSize("".join(attr["-pdf-line-spacing"]))
        # print "line-spacing", attr["-pdf-line-spacing"], frag.leading
    if attr.has_key("white-space"):
        # normal | pre | nowrap
        frag.whiteSpace = str(attr["white-space"]).lower()
    # ALIGN & VALIGN
    if attr.has_key("text-align"):
        frag.alignment = getAlign(attr["text-align"])
    if attr.has_key("vertical-align"):
        frag.vAlign = attr["vertical-align"]
    # HEIGHT & WIDTH
    if attr.has_key("height"):
        frag.height = "".join(toList(attr["height"])) # XXX Relative is not correct!
        if frag.height in ("auto",):
            frag.height = None
    if attr.has_key("width"):
        # print attr["width"]
        frag.width = "".join(toList(attr["width"])) # XXX Relative is not correct!
        if frag.width in ("auto",):
            frag.width = None
    # ZOOM
    if attr.has_key("zoom"):
        # print attr["width"]
        zoom = "".join(toList(attr["zoom"])) # XXX Relative is not correct!
        if zoom.endswith("%"):
            zoom = float(zoom[:-1]) / 100.0
        frag.zoom = float(zoom)
    if attr.has_key("list-style-type"):
        frag.listStyleType = str(attr["list-style-type"]).lower()
    if attr.has_key("text-indent"):
        frag.firstLineIndent = getSize(attr["text-indent"], frag.fontSize)
    return frag
    '''

class CSSBuilder(css.CSSBuilder):

    def atFontFace(self, declarations):
        result = self.ruleset([self.selector('*')], declarations)
        # print "@FONT-FACE", declarations, result
        try:
            data = result[0].values()[0]
            self.doFontFace(data)
        except Exception, e:
            log.warn("@fontface", exc_info=1)
        return {}, {}

    def atPage(self, name, pseudopage, declarations):
        try:
            data = {}
            name = name or "body"
            if declarations:
                result = self.ruleset([self.selector('*')], declarations)
                if declarations:
                    data = result[0].values()[0]
            self.doPage(name, data, pseudopage)
        except Exception, e:
            log.warn("@page", exc_info=1)
        return {}, {}

    def atFrame(self, name, declarations):
        if declarations:
            result = self.ruleset([self.selector('*')], declarations)
            # print "@BOX", name, declarations, result
            try:
                data = result[0]
                if data:
                    data = data.values()[0]
                    self.doFrame(name, data)
            except Exception, e:
                log.warn("@frame", exc_info=1)
        return {}, {}

class CSSParser(css.CSSParser):

    def parseExternal(self, filename):
        # print "@import", self.basepath, filename
        basepath = self.basepath
        file = self.getFile(filename, basepath=self.basepath)
        if not file:
            log.warning("File %r not found", filename)
            return None
        self.basepath = file.basepath
        result = self.parse(file.getData())
        self.basepath = basepath
        return result

def parseCSS(src, getFile=getFile, basepath=None, doPage=None, doFontFace=None, doFrame=None):
    cssBuilder = CSSBuilder(mediumSet=["all", "print", "pdf"])
    cssBuilder.doPage = doPage
    cssBuilder.doFontFace = doFontFace
    cssBuilder.doFrame = doFrame

    cssParser = CSSParser(cssBuilder)
    cssParser.basepath = basepath
    cssParser.getFile = getFile

    cssResult = cssParser.parse(src)
    cssCascade = css.CSSCascadeStrategy(cssResult)
    cssCascade.parser = cssParser
    return cssCascade

def normalizeCSS(value):
    " Strip surrounding comments from CSS "
    value = value.strip()
    if value.startswith("<![CDATA["):
        value = value[9: - 3]
    if value.startswith("<!--"):
        value = value[4: - 3]
    return value.strip()

def loopCSS(node, collect=False):
    " Walk the DOM for CSS informations "
    data = []
    if node.nodeType == Node.TEXT_NODE and collect:
        data.append(node.data)
    elif node.nodeType == Node.ELEMENT_NODE:
        name = node.tagName.lower()
        if name in ("style", "link"):
            attr = getAttributes(node.attributes)
            media = [x.strip() for x in attr.get("media", "").lower().split(",") if x.strip()]
            # print attr, repr(media)
            if (attr.get("type", "").lower() in ("", "text/css") and (
                not media or
                ("all" in media) or
                ("print" in media) or
                ("pdf" in media))):
                if name=="style":
                    # print "CSS STYLE"
                    for node in node.childNodes:
                        data = loopCSS(node, collect=True)
                    return normalizeCSS(data)
                if name=="link" and attr.has_key("href") and attr.get("rel", "").lower()=="stylesheet":
                    # print "CSS LINK", attr
                    data.append('@import "%s" %s;' % (attr["href"], ",".join(media)))
    for node in node.childNodes:
        data.append(loopCSS(node, collect=collect))
    return u"\n".join(data)

def getCSS(dom):
    " Collect all CSS definitions from DOM "
    return loopCSS(dom)

if __name__=="__main__":
    from xhtml2pdf.dom import getDOM

    def _loopDOM(node, indent = ""):
        if node.nodeType == Node.ELEMENT_NODE:
            indent += node.tagName + " "
            print indent, node.style
        for node in node.childNodes:
            _loopDOM(node, indent)

    TEST = """
    <link href="default.css" rel="stylesheet">
    <style type="text/css">
    b { color: red }
    </style>
    Hello <b style="background: yellow;">World</b>
    """
    dom = getDOM(TEST)
    # print dom.toprettyxml()
    cssSource = getCSS(dom)
    print cssSource
    cssCascade = parseCSS(cssSource)
    applyStyles(dom, cssCascade)
    _loopDOM(dom)
